Skip to content

Conversation

@FoamyGuy
Copy link
Collaborator

@FoamyGuy FoamyGuy commented Feb 26, 2025

Tested successfully with this on PyPortal Titano:


import board

import displayio
import terminalio


palette = displayio.Palette(2)
palette[0] = 0x000000
palette[1] = 0xffffff

bbox = terminalio.FONT.get_bounding_box()
tg = displayio.TileGrid(terminalio.FONT.bitmap, pixel_shader=palette,
                        tile_width=bbox[0], tile_height=bbox[1],
                        width=10, height=4, default_tile=0)

for i in range(40):
    tg[i] = i

main_group = displayio.Group()
main_group.append(tg)

board.DISPLAY.root_group = main_group

tg.set_inverted((3,0), True)
print(tg.get_inverted((3,0)))

while True:
    pass

which shows several glyphs from the built-in font and inverts one of them using this new feature.

Also note that this supports only TileGrids which use Palettes that have 2 colors. It is ultimately intended for use along with terminalio.Terminal for cursor visuals within the terminal.

Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IThe use of a tuple for the coordinates seems unusual to me. The constructor takes x and y arguments. Are there other displayio things that take tuples for (x, y)?

@FoamyGuy
Copy link
Collaborator Author

IThe use of a tuple for the coordinates seems unusual to me. The constructor takes x and y arguments. Are there other displayio things that take tuples for (x, y)?

tilegrid.contains()

static mp_obj_t displayio_tilegrid_obj_contains(mp_obj_t self_in, mp_obj_t touch_tuple) {
acceps a tuple for x,y coordinates, although they are display pixel coordinates, not tile index coordinates. Also iirc I added contains() and used a tuple to make it consistent with adafruit_button.contains() which it basically implements for TileGrids to let them act as touch buttons. I believe adafruit_button.contains() accepted a tuple in order to be compatible with values coming out of touch display drivers.

I actually did try using x and y arguments at first though, and I'd be happy to get back to that. I failed at figuring out how to properly do something like MP_DEFINE_CONST_FUN_OBJ_4. I found MP_DEFINE_CONST_FUN_OBJ_VAR which seems like it should do what I needed by passing 4 for min args, but I failed to make it work and ended up switching to a tuple in order to get down to 3 arguments needed so that I could use MP_DEFINE_CONST_FUN_OBJ_3

dhalbert
dhalbert previously approved these changes Feb 26, 2025
Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for the explanation.

@dhalbert dhalbert dismissed their stale review February 26, 2025 18:48

build failures, looks like due to size

@FoamyGuy
Copy link
Collaborator Author

I've got a few ideas in mind that might be able to trim some bits of code. I'll see if I can get it under the limit for the one that overflowed.

@FoamyGuy
Copy link
Collaborator Author

I was able to shave 30 or so bytes by refactoring the nested for loops, and the larger branching if statements. However it's still not enough to fit the feather m0 supersized.

I've disabled rainbowio on that device for now which makes it match the feather m0 express.

If someone can point me towards how to use MP_DEFINE_CONST_FUN_OBJ_VAR or whatever is needed to define a function that has 4 total arguments I can try splitting x and y values back out of the tuples, perhaps losing the code that unpacks the values from tuple might get it closer to fitting, but not sure if it'll be enough, am willing to try.

@dhalbert
Copy link
Collaborator

If you think using the tuple is more consistent with the rest of the API, then I'd say stick with that.

There are a bunch of uses of MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN . I remember being mildly tripped up about counting the self argument or not, I think, but I don't remember the details.

@FoamyGuy
Copy link
Collaborator Author

okay, I'll leave it alone for now. it does also match the tile getter and setter that use square bracket access in python code like tg[(3,0)] = 24 which accept tuples as the value in the brackets and without the parens tg[3,0] = 24 which is essentially the same as I understand it.

@FoamyGuy
Copy link
Collaborator Author

This needs to handle dirty area inside of set_inverted. Currently the change wont become visible unless something else changes causing the area of the tile to get refreshed.

In the example script above it works because it gets set before being rendered the first time I think. But with an added time.sleep() and then changing some invert values they do not take effect currently.

@FoamyGuy
Copy link
Collaborator Author

The latest commit resolves that issue by forcing refresh on tiles when their inverted state changes.

This new example code validates that it behaves as expected:

import time

import board

import displayio
import terminalio

palette = displayio.Palette(2)
palette[0] = 0x000000
palette[1] = 0xffffff

bbox = terminalio.FONT.get_bounding_box()
tg = displayio.TileGrid(terminalio.FONT.bitmap, pixel_shader=palette,
                        tile_width=bbox[0], tile_height=bbox[1],
                        width=10, height=4, default_tile=0)

for i in range(40):
    tg[i] = i

main_group = displayio.Group()
main_group.append(tg)

board.DISPLAY.root_group = main_group

while True:
    for y in range(tg.height):
        for x in range(tg.width):
            cur = (x, y)
            tg.set_inverted(cur, not tg.get_inverted(cur))
            time.sleep(0.025)

@FoamyGuy
Copy link
Collaborator Author

This needed validation on the x,y values in order to avoid hard faulting if the user passes values outside the range of the tilegrid size. After adding those it pushed the feather_m0_supersized over the limit again.

I ended up disabling displayio on that device which leaves plenty of room. I am open to suggestion if there is something else that would be better to disable on that device.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking this on! Unfortunately, I don't think this is the right approach.

Instead, I think it'd be better to make a terminal-specific pixel_shader. Tilegrid passes in the tile coordinates to the pixel shader. The shader can then store the foreground and background color indices for each tile.

This would then leave TileGrid nearly unchanged and not cost more memory for inverts on every TG. It'd also allow recoloring of foreground and background of each character.

What do you think?

void common_hal_displayio_tilegrid_set_inverted(displayio_tilegrid_t *self, uint16_t x, uint16_t y, bool inverted) {
uint16_t tile_location = y * self->width_in_tiles + x;
self->inverts[tile_location] = inverted;
common_hal_displayio_tilegrid_set_tile(self, x, y, common_hal_displayio_tilegrid_get_tile(self, x, y));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather you factor out the code to mark a tile area dirty so you can use it here. That way we can optimize set_tile to skip a dirty area when the tile index is set to the current value.

Comment on lines +510 to +514
if (common_hal_displayio_palette_get_len(self->pixel_shader) == 2) {
if (self->inverts[tile_location]) {
input_pixel.pixel = (input_pixel.pixel + 1) % 2;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this? It'd do something for all palette sizes.

Suggested change
if (common_hal_displayio_palette_get_len(self->pixel_shader) == 2) {
if (self->inverts[tile_location]) {
input_pixel.pixel = (input_pixel.pixel + 1) % 2;
}
}
if (self->inverts[tile_location]) {
input_pixel.pixel = common_hal_displayio_palette_get_len(self->pixel_shader) - 1 - input_pixel.pixel;
}

mp_obj_t pixel_shader, uint16_t width, uint16_t height,
uint16_t tile_width, uint16_t tile_height, uint16_t x, uint16_t y, uint8_t default_tile) {
uint32_t total_tiles = width * height;
self->inverts = (bool *)m_malloc(total_tiles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will allocate one byte per tile. It may save some code size to do so but it'll leave 7 unused bits.

}

bool common_hal_displayio_tilegrid_get_inverted(displayio_tilegrid_t *self, uint16_t x, uint16_t y) {
uint16_t tile_location = y * self->width_in_tiles + x;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:

if (self->inverts == NULL) {
  return false;
}

.top_left_x = {0},
.top_left_y = {0},
.tiles = 0,
.inverts = false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.inverts = false,
.inverts = NULL,

@FoamyGuy
Copy link
Collaborator Author

FoamyGuy commented Mar 2, 2025

Will make a new branch and PR for this with the TilePaletteMapper approach

@FoamyGuy FoamyGuy closed this Mar 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants